/*!
 * wordcloud2.js
 * http://timdream.org/wordcloud2.js/
 *
 * Copyright 2011 - 2019 Tim Guan-tin Chien and contributors.
 * Released under the MIT license
 */

'use strict'

import worker from '@ohos.worker';

// // setImmediate
// if (!window.setImmediate) {
// 	window.setImmediate = (function setupSetImmediate () {
// 		return window.msSetImmediate ||
// 		window.webkitSetImmediate ||
// 		window.mozSetImmediate ||
// 		window.oSetImmediate ||
// 		(function setupSetZeroTimeout () {
// 			if (!window.postMessage || !window.addEventListener) {
// 				return null
// 			}
//
// 			var callbacks = [undefined]
// 			var message = 'zero-timeout-message'
//
// 			// Like setTimeout, but only takes a function argument.  There's
// 			// no time argument (always zero) and no arguments (you have to
// 			// use a closure).
// 			var setZeroTimeout = function setZeroTimeout (callback) {
// 				var id = callbacks.length
// 				callbacks.push(callback)
// 				window.postMessage(message + id.toString(36), '*')
//
// 				return id
// 			}
//
// 			window.addEventListener('message', function setZeroTimeoutMessage (evt) {
// 				// Skipping checking event source, retarded IE confused this window
// 				// object with another in the presence of iframe
// 				if (typeof evt.data !== 'string' ||
// 					evt.data.substr(0, message.length) !== message/* ||
//             evt.source !== window */) {
// 					return
// 				}
//
// 				evt.stopImmediatePropagation()
//
// 				var id = parseInt(evt.data.substr(message.length), 36)
// 				if (!callbacks[id]) {
// 					return
// 				}
//
// 				callbacks[id]()
// 				callbacks[id] = undefined
// 			}, true)
//
// 			/* specify clearImmediate() here since we need the scope */
// 			window.clearImmediate = function clearZeroTimeout (id) {
// 				if (!callbacks[id]) {
// 					return
// 				}
//
// 				callbacks[id] = undefined
// 			}
//
// 			return setZeroTimeout
// 		})() ||
// 			// fallback
// 			function setImmediateFallback (fn) {
// 				window.setTimeout(fn, 0)
// 			}
// 	})()
// }
//
// if (!window.clearImmediate) {
// 	window.clearImmediate = (function setupClearImmediate () {
// 		return window.msClearImmediate ||
// 		window.webkitClearImmediate ||
// 		window.mozClearImmediate ||
// 		window.oClearImmediate ||
// 			// "clearZeroTimeout" is implement on the previous block ||
// 			// fallback
// 			function clearImmediateFallback (timer) {
// 				window.clearTimeout(timer)
// 			}
// 	})()
// }

// Based on http://jsfromhell.com/array/shuffle
var shuffleArray = function shuffleArray (arr) {
	for (var j, x, i = arr.length; i;) {
		j = Math.floor(Math.random() * i)
		x = arr[--i]
		arr[i] = arr[j]
		arr[j] = x
	}
	return arr
}

// Check if WordCloud can run on this browser
var isSupported = (function isSupported () {
	return true
}())

// Find out if the browser impose minium font size by
// drawing small texts on a canvas and measure it's width.
var minFontSize = (function getMinFontSize () {
	if (!isSupported) {
		return
	}

	var tempCanvas = new OffscreenCanvasRenderingContext2D(500,500);
	var ctx = tempCanvas

	// start from 20
	var size = 20

	// two sizes to measure
	var hanWidth, mWidth

	while (size) {
		ctx.font = size.toString(10) + 'px sans-serif'
		if ((ctx.measureText('\uFF37').width === hanWidth) &&
			(ctx.measureText('m').width) === mWidth) {
			return (size + 1)
		}

		hanWidth = ctx.measureText('\uFF37').width
		mWidth = ctx.measureText('m').width

		size--
	}

	return 0
})()

var getItemExtraData = function (item) {
	if (Array.isArray(item)) {
		var itemCopy = item.slice()
		// remove data we already have (word and weight)
		itemCopy.splice(0, 2)
		return itemCopy
	} else {
		return []
	}
}

var timer = {};

// 添加自定义的定时器实现
const customTimer = {
	timers: {},
	timerId: 0,

	setTimeout: (callback, delay) => {
		const id = customTimer.timerId++;
		customTimer.timers[id] = {
			callback: callback,
			time: Date.now() + delay
		};
		return id;
	},

	clearTimeout: (id) => {
		delete customTimer.timers[id];
	},

	// 检查并执行到期的定时器
	check: () => {
		const now = Date.now();
		for (const id in customTimer.timers) {
			if (customTimer.timers[id].time <= now) {
				const callback = customTimer.timers[id].callback;
				customTimer.clearTimeout(id);
				callback();
			}
		}
	}
};

// 启动定时器检查循环
setInterval(() => {
	customTimer.check();
}, 10);

class WordCloud {
	constructor(canvas, options) {
		this.canvas = canvas;
		this.worker = new worker.Worker('entry/ets/workers/wordcloud.worker.ts');

		this.vp2px = vp2px(1)
		if (!isSupported) {
			return
		}

		var timerId = Math.floor(Math.random() * Date.now())

		/* Default values to be overwritten by options object */
		var settings = {
			list: [],
			fontFamily: '"Trebuchet MS", "Heiti TC", "微軟正黑體", ' +
				'"Arial Unicode MS", "Droid Fallback Sans", sans-serif',
			fontWeight: 'normal',
			color: 'random-dark',
			minSize: 0, // 0 to disable
			weightFactor: 1,
			clearCanvas: true,
			backgroundColor: '#fff', // opaque white = rgba(255, 255, 255, 1)

			gridSize: 8,
			drawOutOfBound: false,
			shrinkToFit: false,
			origin: null,

			drawMask: false,
			maskColor: 'rgba(255,0,0,0.3)',
			maskGapWidth: 0.3,

			wait: 0,
			abortThreshold: 0, // disabled
			abort: function noop () {},

			minRotation: -Math.PI / 2,
			maxRotation: Math.PI / 2,
			rotationSteps: 0,

			shuffle: true,
			rotateRatio: 0.1,

			shape: 'circle',
			ellipticity: 0.65,

			classes: null,

			hover: null,
			click: null
		}

		if (options) {
			for (var key in options) {
				if (key in settings) {
					settings[key] = options[key]
				}
			}
		}

		/* Convert weightFactor into a function */
		if (typeof settings.weightFactor !== 'function') {
			var factor = settings.weightFactor
			settings.weightFactor = function weightFactor (pt) {
				return pt * factor // in px
			}
		}

		/* Convert shape into a function */
		if (typeof settings.shape !== 'function') {
			switch (settings.shape) {
				case 'circle':
				/* falls through */
				default:
				// 'circle' is the default and a shortcut in the code loop.
					settings.shape = 'circle'
					break

				case 'cardioid':
					settings.shape = function shapeCardioid (theta) {
						return 1 - Math.sin(theta)
					}
					break

			/*
            To work out an X-gon, one has to calculate "m",
            where 1/(cos(2*PI/X)+m*sin(2*PI/X)) = 1/(cos(0)+m*sin(0))
            http://www.wolframalpha.com/input/?i=1%2F%28cos%282*PI%2FX%29%2Bm*sin%28
            2*PI%2FX%29%29+%3D+1%2F%28cos%280%29%2Bm*sin%280%29%29
            Copy the solution into polar equation r = 1/(cos(t') + m*sin(t'))
            where t' equals to mod(t, 2PI/X)
           */

				case 'diamond':
					// http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
					// %28t%2C+PI%2F2%29%29%2Bsin%28mod+%28t%2C+PI%2F2%29%29%29%2C+t+%3D
					// +0+..+2*PI
					settings.shape = function shapeSquare (theta) {
						var thetaPrime = theta % (2 * Math.PI / 4)
						return 1 / (Math.cos(thetaPrime) + Math.sin(thetaPrime))
					}
					break

				case 'square':
					// http://www.wolframalpha.com/input/?i=plot+r+%3D+min(1%2Fabs(cos(t
					// )),1%2Fabs(sin(t)))),+t+%3D+0+..+2*PI
					settings.shape = function shapeSquare (theta) {
						return Math.min(
							1 / Math.abs(Math.cos(theta)),
							1 / Math.abs(Math.sin(theta))
						)
					}
					break

				case 'triangle-forward':
					// http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
					// %28t%2C+2*PI%2F3%29%29%2Bsqrt%283%29sin%28mod+%28t%2C+2*PI%2F3%29
					// %29%29%2C+t+%3D+0+..+2*PI
					settings.shape = function shapeTriangle (theta) {
						var thetaPrime = theta % (2 * Math.PI / 3)
						return 1 / (Math.cos(thetaPrime) +
							Math.sqrt(3) * Math.sin(thetaPrime))
					}
					break

				case 'triangle':
				case 'triangle-upright':
					settings.shape = function shapeTriangle (theta) {
						var thetaPrime = (theta + Math.PI * 3 / 2) % (2 * Math.PI / 3)
						return 1 / (Math.cos(thetaPrime) +
							Math.sqrt(3) * Math.sin(thetaPrime))
					}
					break

				case 'pentagon':
					settings.shape = function shapePentagon (theta) {
						var thetaPrime = (theta + 0.955) % (2 * Math.PI / 5)
						return 1 / (Math.cos(thetaPrime) +
							0.726543 * Math.sin(thetaPrime))
					}
					break

				case 'star':
					settings.shape = function shapeStar (theta) {
						var thetaPrime = (theta + 0.955) % (2 * Math.PI / 10)
						if ((theta + 0.955) % (2 * Math.PI / 5) - (2 * Math.PI / 10) >= 0) {
							return 1 / (Math.cos((2 * Math.PI / 10) - thetaPrime) +
								3.07768 * Math.sin((2 * Math.PI / 10) - thetaPrime))
						} else {
							return 1 / (Math.cos(thetaPrime) +
								3.07768 * Math.sin(thetaPrime))
						}
					}
					break
			}
		}

		/* Make sure gridSize is a whole number and is not smaller than 4px */
		settings.gridSize = Math.max(Math.floor(settings.gridSize), 4)

		/* shorthand */
		var g = settings.gridSize
		var maskRectWidth = g - settings.maskGapWidth

		/* normalize rotation settings */
		var rotationRange = Math.abs(settings.maxRotation - settings.minRotation)
		var rotationSteps = Math.abs(Math.floor(settings.rotationSteps))
		var minRotation = Math.min(settings.maxRotation, settings.minRotation)

		/* information/object available to all functions, set when start() */
		var grid, // 2d array containing filling information
			ngx, ngy, // width and height of the grid
			center, // position of the center of the cloud
			maxRadius

		/* timestamp for measuring each putWord() action */
		var escapeTime

		/* function for getting the color of the text */
		var getTextColor
		function randomHslColor (min, max) {
			// 生成 HSL 分量
			const h = parseInt((Math.random() * 360).toFixed());
			const s = parseInt((Math.random() * 30 + 70).toFixed());
			const l = parseInt((Math.random() * (max - min) + min).toFixed());

			// 将 HSL 转换为 RGB
			const hslToRgb = (h, s, l) => {
				h /= 360;
				s /= 100;
				l /= 100;

				let r = 0, g = 0, b = 0;
				if (s === 0) {
					r = g = b = l; // 灰色
				} else {
					const hue2rgb = (p, q, t) => {
						if (t < 0) {
							t += 1;
						}
						if (t > 1) {
							t -= 1;
						}
						if (t < 1 / 6) {
							return p + (q - p) * 6 * t;
						}
						if (t < 1 / 2) {
							return q;
						}
						if (t < 2 / 3) {
							return p + (q - p) * (2 / 3 - t) * 6;
						}
						return p;
					};

					const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
					const p = 2 * l - q;
					r = hue2rgb(p, q, h + 1 / 3);
					g = hue2rgb(p, q, h);
					b = hue2rgb(p, q, h - 1 / 3);
				}

				return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
			};

			let result = hslToRgb(h, s, l);
			// 转为十六进制字符串
			const r = result[0];
			const g = result[1];
			const b = result[2];
			const toHex = (n) => n.toString(16).padStart(2, '0').toUpperCase();

			return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
		}
		switch (settings.color) {
			case 'random-dark':
				getTextColor = function getRandomDarkColor () {
					return randomHslColor(10, 50)
				}
				break

			case 'random-light':
				getTextColor = function getRandomLightColor () {
					return randomHslColor(50, 90)
				}
				break

			default:
				if (typeof settings.color === 'function') {
					getTextColor = settings.color
				}
				break
		}

		/* function for getting the font-weight of the text */
		var getTextFontWeight
		if (typeof settings.fontWeight === 'function') {
			getTextFontWeight = settings.fontWeight
		}

		/* function for getting the classes of the text */
		var getTextClasses = null
		if (typeof settings.classes === 'function') {
			getTextClasses = settings.classes
		}

		/* Interactive */
		var interactive = false
		var infoGrid = []
		var hovered

		var getInfoGridFromMouseTouchEvent =
			function getInfoGridFromMouseTouchEvent (evt) {
				var canvas = evt.currentTarget
				var rect = canvas.getBoundingClientRect()
				var clientX
				var clientY
				/** Detect if touches are available */
				if (evt.touches) {
					clientX = evt.touches[0].clientX
					clientY = evt.touches[0].clientY
				} else {
					clientX = evt.clientX
					clientY = evt.clientY
				}

				var eventXvalue = clientX - rect.left
				var eventX = eventXvalue < 0 ? 0 : eventXvalue
				var eventY = clientY - rect.top

				var x = Math.floor(eventX * ((canvas.width / rect.width) || 1) / g)
				var y = Math.floor(eventY * ((canvas.height / rect.height) || 1) / g)

				if (!infoGrid[x]) {
					return null
				}

				return infoGrid[x][y]
			}

		var wordcloudhover = function wordcloudhover (evt) {
			var info = getInfoGridFromMouseTouchEvent(evt)

			if (hovered === info) {
				return
			}

			hovered = info
			if (!info) {
				settings.hover(undefined, undefined, evt)

				return
			}

			settings.hover(info.item, info.dimension, evt)
		}

		var wordcloudclick = function wordcloudclick (evt) {
			var info = getInfoGridFromMouseTouchEvent(evt)
			if (!info) {
				return
			}

			settings.click(info.item, info.dimension, evt)
			evt.preventDefault()
		}

		/* Get points on the grid for a given radius away from the center */
		var pointsAtRadius = []
		var getPointsAtRadius = function getPointsAtRadius (radius) {
			if (pointsAtRadius[radius]) {
				return pointsAtRadius[radius]
			}

			// Look for these number of points on each radius
			var T = radius * 8

			// Getting all the points at this radius
			var t = T
			var points = []

			if (radius === 0) {
				points.push([center[0], center[1], 0])
			}

			while (t--) {
				// distort the radius to put the cloud in shape
				var rx = 1
				if (settings.shape !== 'circle') {
					rx = settings.shape(t / T * 2 * Math.PI) // 0 to 1
				}

				// Push [x, y, t] t is used solely for getTextColor()
				points.push([
					center[0] + radius * rx * Math.cos(-t / T * 2 * Math.PI),
					center[1] + radius * rx * Math.sin(-t / T * 2 * Math.PI) *
					settings.ellipticity,
					t / T * 2 * Math.PI])
			}

			pointsAtRadius[radius] = points
			return points
		}

		/* Return true if we had spent too much time */
		var exceedTime = function exceedTime () {
			return ((settings.abortThreshold > 0) &&
				((new Date()).getTime() - escapeTime > settings.abortThreshold))
		}

		/* Get the deg of rotation according to settings, and luck. */
		var getRotateDeg = function getRotateDeg () {
			if (settings.rotateRatio === 0) {
				return 0
			}

			if (Math.random() > settings.rotateRatio) {
				return 0
			}

			if (rotationRange === 0) {
				return minRotation
			}

			if (rotationSteps > 0) {
				// Min rotation + zero or more steps * span of one step
				return minRotation +
					Math.floor(Math.random() * rotationSteps) *
						rotationRange / (rotationSteps - 1)
			} else {
				return minRotation + Math.random() * rotationRange
			}
		}

		this.getTextInfo = function getTextInfo(word, weight, rotateDeg, extraDataArray) {
			return new Promise((resolve) => {
				this.worker.postMessage({
					word,
					weight,
					rotateDeg,
					settings: {
						weightFactor: settings.weightFactor,
						minSize: settings.minSize,
						minFontSize: minFontSize,
						fontWeight: settings.fontWeight,
						fontFamily: settings.fontFamily,
						gridSize: settings.gridSize
					},
					canvasWidth: this.canvas.width,
					canvasHeight: this.canvas.height
				});

				this.worker.onmessage = (e) => {
					const info = e.data;
					if (!info) {
						resolve(false);
						return;
					}
					resolve(info);
				};
			});
		}.bind(this);

		/* Determine if there is room available in the given dimension */
		var canFitText = function canFitText (gx, gy, gw, gh, occupied) {
			// Go through the occupied points,
			// return false if the space is not available.
			var i = occupied.length
			while (i--) {
				var px = gx + occupied[i][0]
				var py = gy + occupied[i][1]

				if (px >= ngx || py >= ngy || px < 0 || py < 0) {
					if (!settings.drawOutOfBound) {
						return false
					}
					continue
				}

				if (!grid[px][py]) {
					return false
				}
			}
			return true
		}

		/* Actually draw the text on the grid */
		this.drawText = function(gx, gy, info, word, weight, distance, theta, rotateDeg, attributes, extraDataArray) {
			var fontSize = info.fontSize
			var color
			if (getTextColor) {
				color = getTextColor(word, weight, fontSize, distance, theta, extraDataArray)
			} else {
				color = settings.color
			}

			var fontWeight
			if (getTextFontWeight) {
				fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray)
			} else {
				fontWeight = settings.fontWeight
			}

			var classes
			if (getTextClasses) {
				classes = getTextClasses(word, weight, fontSize, extraDataArray)
			} else {
				classes = settings.classes
			}

			var ctx = this.canvas
			var mu = info.mu

			// 添加位置信息日志
			console.log(`绘制文字: "${word}", 位置: (${gx}, ${gy}), 字体大小: ${fontSize}, 旋转角度: ${rotateDeg}, 权重: ${weight}`);

			// Save the current state before messing it
			ctx.save()
			ctx.scale(1 / mu, 1 / mu)

			ctx.font = fontWeight + ' ' +
			(fontSize * mu).toString(10) + 'px ' + settings.fontFamily
			ctx.fillStyle = color

			// Translate the canvas position to the origin coordinate of where
			// the text should be put.
			ctx.translate(
				(gx + info.gw / 2) * g * mu,
				(gy + info.gh / 2) * g * mu
			)

			if (rotateDeg !== 0) {
				ctx.rotate(-rotateDeg)
			}

			// Finally, fill the text.
			ctx.textBaseline = 'middle'
			ctx.fillText(
				word, info.fillTextOffsetX * mu,
				(info.fillTextOffsetY + fontSize * 0.5) * mu
			)

			ctx.restore()
		}.bind(this);

		/* Help function to updateGrid */
		this.fillGridAt = function(x, y, drawMask, dimension, item) {
			if (x >= ngx || y >= ngy || x < 0 || y < 0) {
				return
			}

			grid[x][y] = false

			if (drawMask) {
				var ctx = this.canvas
				ctx.fillRect(x * g, y * g, maskRectWidth, maskRectWidth)
			}

			if (interactive) {
				infoGrid[x][y] = { item: item, dimension: dimension }
			}
		}.bind(this);

		/* Update the filling information of the given space with occupied points.
           Draw the mask on the canvas if necessary. */
		this.updateGrid = function(gx, gy, gw, gh, info, item) {
			var occupied = info.occupied
			var drawMask = settings.drawMask
			var ctx
			if (drawMask) {
				ctx = this.canvas
				ctx.save()
				ctx.fillStyle = settings.maskColor
			}

			var dimension
			if (interactive) {
				var bounds = info.bounds
				dimension = {
					x: (gx + bounds[3]) * g,
					y: (gy + bounds[0]) * g,
					w: (bounds[1] - bounds[3] + 1) * g,
					h: (bounds[2] - bounds[0] + 1) * g
				}
			}

			var i = occupied.length
			while (i--) {
				var px = gx + occupied[i][0]
				var py = gy + occupied[i][1]

				if (px >= ngx || py >= ngy || px < 0 || py < 0) {
					continue
				}

				this.fillGridAt(px, py, drawMask, dimension, item)
			}

			if (drawMask) {
				ctx.restore()
			}
		}.bind(this);
		/* putWord() processes each item on the list,
           calculate it's size and determine it's position, and actually
           put it on the canvas. */
		this.putWord = async function(item) {
			var word, weight, attributes
			if (Array.isArray(item)) {
				word = item[0]
				weight = item[1]
			} else {
				word = item.word
				weight = item.weight
				attributes = item.attributes
			}
			var rotateDeg = getRotateDeg()

			var extraDataArray = getItemExtraData(item)

			// 使用Worker获取文字信息
			var info = await this.getTextInfo(word, weight, rotateDeg, extraDataArray)

			if (!info) {
				return false
			}

			if (exceedTime()) {
				return false
			}

			// If drawOutOfBound is set to false,
			// skip the loop if we have already know the bounding box of
			// word is larger than the canvas.
			if (!settings.drawOutOfBound && !settings.shrinkToFit) {
				var bounds = info.bounds;
				if ((bounds[1] - bounds[3] + 1) > ngx ||
					(bounds[2] - bounds[0] + 1) > ngy) {
					return false
				}
			}

			// Determine the position to put the text by
			// start looking for the nearest points
			var r = maxRadius + 1

			var tryToPutWordAtPoint = function(gxy) {
				var gx = Math.floor(gxy[0] - info.gw / 2)
				var gy = Math.floor(gxy[1] - info.gh / 2)
				var gw = info.gw
				var gh = info.gh

				// 添加尝试位置日志
				console.log(`尝试在位置 (${gx}, ${gy}) 放置文字 "${word}"`);

				// If we cannot fit the text at this position, return false
				// and go to the next position.
				if (!canFitText(gx, gy, gw, gh, info.occupied)) {
					console.log(`位置 (${gx}, ${gy}) 无法放置文字 "${word}"`);
					return false
				}

				// Actually put the text on the canvas
				this.drawText(gx, gy, info, word, weight,
					(maxRadius - r), gxy[2], rotateDeg, attributes, extraDataArray)

				// Mark the spaces on the grid as filled
				this.updateGrid(gx, gy, gw, gh, info, item)

				console.log(`成功在位置 (${gx}, ${gy}) 放置文字 "${word}"`);
				// Return true so some() will stop and also return true.
				return true
			}.bind(this)

			while (r--) {
				var points = getPointsAtRadius(maxRadius - r)

				if (settings.shuffle) {
					points = [].concat(points)
					shuffleArray(points)
				}

				// Try to fit the words by looking at each point.
				// array.some() will stop and return true
				// when putWordAtPoint() returns true.
				// If all the points returns false, array.some() returns false.
				var drawn = points.some(tryToPutWordAtPoint)

				if (drawn) {
					// leave putWord() and return true
					return true
				}
			}
			if (settings.shrinkToFit) {
				console.log(`文字 "${word}" 尝试缩小后重新放置`);
				if (Array.isArray(item)) {
					item[1] = item[1] * 3 / 4
				} else {
					item.weight = item.weight * 3 / 4
				}
				return this.putWord(item)
			}
			console.log(`文字 "${word}" 无法放置，放弃`);
			return false
		}.bind(this);

		/* Start drawing on a canvas */
		this.start = async function() {
			var canvas = this.canvas

			if (canvas) {
				ngx = Math.ceil(canvas.width / g)
				ngy = Math.ceil(canvas.height / g)
			} else {
				var rect = canvas.getBoundingClientRect()
				ngx = Math.ceil(rect.width / g)
				ngy = Math.ceil(rect.height / g)
			}

			// Sending a wordcloudstart event which cause the previous loop to stop.
			// Do nothing if the event is canceled.
			// if (!sendEvent('wordcloudstart', true)) {
			// 	return
			// }

			// Determine the center of the word cloud
			center = (settings.origin)
				? [settings.origin[0] / g, settings.origin[1] / g]
				: [ngx / 2, ngy / 2]

			// Maxium radius to look for space
			maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy))

			/* Clear the canvas only if the clearCanvas is set,
               if not, update the grid to the current canvas state */
			grid = []

			var gx, gy, i
			if (!canvas || settings.clearCanvas) {
				var ctx = canvas
				ctx.fillStyle = settings.backgroundColor
				ctx.clearRect(0, 0, ngx * (g + 1), ngy * (g + 1))
				ctx.fillRect(0, 0, ngx * (g + 1), ngy * (g + 1))

				/* fill the grid with empty state */
				gx = ngx
				while (gx--) {
					grid[gx] = []
					gy = ngy
					while (gy--) {
						grid[gx][gy] = true
					}
				}
			} else {
				/* Determine bgPixel by creating
                   another canvas and fill the specified background color. */
				var bctx = this.canvas

				bctx.fillStyle = settings.backgroundColor
				bctx.fillRect(0, 0, 1, 1)
				var bgPixel = bctx.getImageData(0, 0, 1, 1).data

				/* Read back the pixels of the canvas we got to tell which part of the
                   canvas is empty.
                   (no clearCanvas only works with a canvas, not divs) */
				var imageData =
					this.canvas.getImageData(0, 0, ngx * g, ngy * g).data

				gx = ngx
				var x, y
				while (gx--) {
					grid[gx] = []
					gy = ngy
					while (gy--) {
						y = g
						/* eslint no-labels: ["error", { "allowLoop": true }] */
						singleGridLoop: while (y--) {
							x = g
							while (x--) {
								i = 4
								while (i--) {
									if (imageData[((gy * g + y) * ngx * g +
										(gx * g + x)) * 4 + i] !== bgPixel[i]) {
										grid[gx][gy] = false
										break singleGridLoop
									}
								}
							}
						}
						if (grid[gx][gy] !== false) {
							grid[gx][gy] = true
						}
					}
				}

				imageData = bctx = bgPixel = undefined
			}

			// fill the infoGrid with empty state if we need it
			if (settings.hover || settings.click) {
				interactive = true

				/* fill the grid with empty state */
				gx = ngx + 1
				while (gx--) {
					infoGrid[gx] = []
				}

				// if (settings.hover) {
				// 	canvas.addEventListener('mousemove', wordcloudhover)
				// }
				//
				// if (settings.click) {
				// 	canvas.addEventListener('click', wordcloudclick)
				// 	canvas.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)'
				// }

				// canvas.addEventListener('wordcloudstart', function stopInteraction () {
				// 	canvas.removeEventListener('wordcloudstart', stopInteraction)
				// 	canvas.removeEventListener('mousemove', wordcloudhover)
				// 	canvas.removeEventListener('click', wordcloudclick)
				// 	hovered = undefined
				// })
			}

			i = 0
			var loopingFunction, stoppingFunction
			if (settings.wait !== 0) {
				loopingFunction = customTimer.setTimeout
				stoppingFunction = customTimer.clearTimeout
			} else {
				loopingFunction = function(callback) {
					return customTimer.setTimeout(callback, 0);
				}
				stoppingFunction = customTimer.clearTimeout
			}

			// var addEventListener = function addEventListener (type, listener) {
			// 	elements.forEach(function (el) {
			// 		el.addEventListener(type, listener)
			// 	}, this)
			// }

			// var removeEventListener = function removeEventListener (type, listener) {
			// 	elements.forEach(function (el) {
			// 		el.removeEventListener(type, listener)
			// 	}, this)
			// }

			var anotherWordCloudStart = function anotherWordCloudStart () {
				// removeEventListener('wordcloudstart', anotherWordCloudStart)
				stoppingFunction(timer[timerId])
			}

			// addEventListener('wordcloudstart', anotherWordCloudStart)
			timer[timerId] = loopingFunction(async function loop() {
				if (i >= settings.list.length) {
					stoppingFunction(timer[timerId])
					delete timer[timerId];
					return
				}
				escapeTime = (new Date()).getTime()
				var drawn = await this.putWord(settings.list[i])
				if (exceedTime()) {
					stoppingFunction(timer[timerId])
					settings.abort()
					delete timer[timerId]
					return
				}
				i++
				timer[timerId] = loopingFunction(loop.bind(this), settings.wait)
			}.bind(this), settings.wait)
		}.bind(this);

		// 在组件销毁时终止Worker
		this.destroy = function() {
			if (this.worker) {
				this.worker.terminate();
			}
		};

		// All set, start the drawing
		this.start();
	}

	static isSupported = isSupported;
	static minFontSize = minFontSize;
	static stop() {
		if (timer) {
			for (var timerId in timer) {
				customTimer.clearTimeout(timer[timerId])
			}
		}
	}
}

export { WordCloud };

